#include "PreApplication.h"
#include "Application.h"
#include "InputListener.h"
#include "Camera.h"
#include "Client.h"
#include "windowsx.h"
#include "winuser.h"

Application * g_app = 0;
Application* getApp()
{
	return g_app;
}

static LRESULT CALLBACK WndProc(
							HWND hWnd,
							UINT msg,
							WPARAM wParam,
							LPARAM lParam
							)
{
	if(g_app){
		LRESULT res;
		if(g_app->wndProc(msg,wParam,lParam,res))
			return res;
	}
	return ::DefWindowProc(hWnd, msg, wParam, lParam);
}



POINT absMouseOffset;
POINT savedMousePt;

Point<int> ptLocked; // in engine space.

// transform coordinates from window client to engine.
static inline Point<int> _pointWinClientToEngine(HWND hwnd, const POINT &pt)
{
	RECT rc;
	::GetClientRect(hwnd, &rc);

	int x = pt.x;
	int y = (rc.bottom - rc.top) - pt.y - 1;

	return Point<int>(x, y);
}

// transform coordinates from engine to window client
static inline POINT _pointEngineToWinClient(HWND hwnd, const Point<int> &pt)
{
	RECT rc;
	::GetClientRect(hwnd, &rc);

	POINT winPt;
	winPt.x = pt.x;
	winPt.y = (rc.bottom - rc.top) - pt.y - 1;

	return winPt;
}

static inline Dword toEngineMouseFlags(DWORD nFlags)
{
	Dword flags = 0;
	if (nFlags & MK_LBUTTON)
		flags |= MBM_Left;
	if (nFlags & MK_RBUTTON)
		flags |= MBM_Right;
	if (nFlags & MK_MBUTTON)
		flags |= MBM_Middle;

	return flags;
}


Application::Application(void)
{
	g_app = this;
	mClient = NULL;
	mRenderSys = NULL;
	mCurInputListener = NULL;
	mScene = NULL;
}

Application::~Application(void)
{
}

bool Application::start(HINSTANCE hInstance)
{

	bWillEnd = false;

	int width = 640;
	int height = 480;


	WNDCLASS wc;

	wc.style         = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc   = (WNDPROC)WndProc; 
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hInstance;
	wc.hIcon         = LoadIcon(0, IDI_APPLICATION);
	wc.hCursor       = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = 0;
	wc.lpszClassName = "Direct3D9App";

	if( !RegisterClass(&wc) ) 
	{
		::MessageBox(0, "RegisterClass() - FAILED", 0, 0);
		return false;
	}

	mHwnd = 0;
	mHwnd = ::CreateWindow("Direct3D9App", "Direct3D9App", 
		WS_OVERLAPPEDWINDOW,//WS_EX_TOPMOST,
		0, 0, width, height,
		0 /*parent hwnd*/, 0 /* menu */, hInstance, 0 /*extra*/);

	if( !mHwnd )
	{
		::MessageBox(0, "CreateWindow() - FAILED", 0, 0);
		return false;
	}

	::ShowWindow(mHwnd, SW_SHOW);
	::UpdateWindow(mHwnd);

	createCamera();

	mRenderSys = new RenderSystem;
	mRenderSys->create(hInstance,mHwnd,
		width,height,true,D3DDEVTYPE_HAL);


	mScene = new Scene;
	if(mClient)
		mClient->start();
	return true;
}

void Application::createCamera()
{

	mCamera = new Camera;
	mCamera->setAspect(800.0f/600.0f);
	mCamera->setNearPlane(1.0f);
	mCamera->setFarPlane(1000.f);
	mCamera->setFovY(Radian(Math::PI/2));
	mCamera->setDirection(Vector3(0,1,0));
	mCamera->setPosition(Vector3(0,-10,0));
}

bool Application::loop()
{
	MSG msg;
	::ZeroMemory(&msg, sizeof(MSG));

	static float lastTime = (float)timeGetTime();

	while(msg.message != WM_QUIT && !bWillEnd)
	{
		if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
		else
		{ 
			float currTime  = (float)timeGetTime();
			float timeDelta = (currTime - lastTime)*0.001f;

			//Display(timeDelta);
			if(frame())
				break;
			draw3D();
			draw2D();

			lastTime = currTime;
		}
	}
	//return (int)msg.wParam;
	return true;
}

bool Application::frame()
{
	if(mClient)
		mClient->frame();
	return false;
}

void Application::draw3D()
{
	if(mScene)
		mScene->draw(mRenderSys,mCamera);
	if(mClient)
		mClient->draw3D();
}

void Application::draw2D()
{
	if(mClient)
		mClient->draw2D();
}

bool Application::end()
{
	if(mClient)
		mClient->end();

	if(mScene){
		delete mScene;
		mScene = NULL;
	}

	if(mRenderSys){
		mRenderSys->destroy();
		delete mRenderSys;
		mRenderSys = 0;
	}

	if(mCamera)
		delete mCamera;

	return true;
}

void Application::setClient(Client * client)
{
	mClient = client;
}

void Application::setInputListener(InputListener * inputListener)
{
	mCurInputListener = inputListener;
}

bool Application::wndProc(UINT message, WPARAM wParam, LPARAM lParam, LRESULT &res)
{
	res = 0;
	bool processed = false;

	// InputListener::InputMessage msg;

	switch (message) 
	{
	//case WM_CREATE:
	//	break;
	//case WM_PAINT:
	//	//if (!Exception::isInException)
	//	//	update();
	//	break;
	//case WM_CLOSE:
	//	m_isReady = false;
	//	// PostQuitMessage(0);
	//	break;
	//case WM_DESTROY:
	//	//m_isReady = false;
	//	// destroys engine.
	//	//destroy();

	//	//m_hWnd = 0;
	//	break;

	//case WM_ACTIVATEAPP:
	//	//m_bHasFocus = (BOOL) wParam;
	//	m_isActive = (BOOL)wParam == TRUE;
	//	break;

	//case WM_ENTERSIZEMOVE:
	//	// Halt frame movement while the app is sizing or moving
	//	//pause( true);
	//	break;

	//case WM_SIZE:
	//case WM_EXITSIZEMOVE: {
	//	bool bSizeByMinimize = message == WM_SIZE && SIZE_MINIMIZED == wParam;		
	//	if (!bSizeByMinimize) { // do not resize if window minimized.
	//		RECT rcClient;
	//		GetClientRect( m_hWnd, &rcClient);

	//		int cx, cy;
	//		cx = rcClient.right - rcClient.left;
	//		cy = rcClient.bottom - rcClient.top;

	//		Math::clampMin(cx, 1);
	//		Math::clampMin(cy, 1);

	//		resize(cx, cy);
	//	}
	//					  }
	//					  break;	
	//case WM_MOVE: {
	//	RECT rc;
	//	::GetWindowRect(m_hWnd, &rc);
	//	int screenCy = ::GetSystemMetrics(SM_CYSCREEN);
	//	moveTo(rc.left, screenCy - rc.bottom);
	//			  }
	//			  break;
	case WM_SHOWWINDOW:
		// do not handle input message if hide.
		//InputListener::m_needMessage = (wParam == 1);
		break;

	case WM_KEYDOWN: 
	{
		int bFirstDown = !(lParam&(1 << 30));
		if (bFirstDown){
			if(mCurInputListener)
				mCurInputListener->onKeyDown((InputKey)wParam);
		}
		break;
	}
	case WM_KEYUP:
		if(mCurInputListener) mCurInputListener->onKeyUp((InputKey)wParam);
		break;

	//case WM_CHAR: {
	//	onChar((Dword)wParam);
	//			  }
	//			  break;

	case WM_MOUSEMOVE:
	case WM_LBUTTONDOWN:
	case WM_LBUTTONUP:
	case WM_MBUTTONDOWN:
	case WM_MBUTTONUP:
	case WM_RBUTTONDOWN:
	case WM_RBUTTONUP:
	case WM_MOUSEWHEEL:
		{
			if (true/*useWinInputMessage*/) {
				POINT ptWin;
				Point<int> pt;
				Dword flags;

				switch (message) {
	case WM_MOUSEMOVE: {
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(wParam);

		POINT newPt;
		newPt = ptWin;
		::ClientToScreen(mHwnd, &newPt);

		// lazy update savedMousePt. avoid minimize->lock->restore error. see clipCursor.
		//if (isMouseLocked) {
		//	// move mouse to locked point.
		//	{
		//		POINT winPt = _pointEngineToWinClient(mHwnd, ptLocked);
		//		::ClientToScreen(mHwnd, &winPt);

		//		::SetCursorPos(winPt.x, winPt.y);
		//		savedMousePt = winPt;

		//		// remove WM_MOUSEMOVE caused by ::SetCursorPos
		//		MSG msg;
		//		while (::PeekMessage(&msg, mHwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) {};
		//		//std::cout << "onMouseMove:" << std::endl;
		//	}
		//}

		Point<int> delta;
		if (false/*m_mouseCaptured*/) {
			int screenCx = ::GetSystemMetrics(SM_CXSCREEN);
			int screenCy = ::GetSystemMetrics(SM_CYSCREEN);

			bool isTouchEdge = false;

			// x pos.
			if (newPt.x == 0) {
				delta.x = newPt.x - savedMousePt.x;
				newPt.x += screenCx - 2;
				isTouchEdge = true;
			}
			else if (newPt.x == screenCx - 1) {
				delta.x = newPt.x - savedMousePt.x;
				newPt.x -= (screenCx - 2);
				isTouchEdge = true;
			}
			else
				delta.x = newPt.x - savedMousePt.x;							

			// y pos.
			if (newPt.y == 0) {
				delta.y = newPt.y - savedMousePt.y;
				newPt.y += screenCy - 2;
				isTouchEdge = true;
			}
			else if (newPt.y == screenCy - 1) {
				delta.y = newPt.y - savedMousePt.y;
				newPt.y -= (screenCy - 2);
				isTouchEdge = true;
			}
			else
				delta.y = newPt.y - savedMousePt.y;

			if (isTouchEdge)
				::SetCursorPos(newPt.x, newPt.y);

			absMouseOffset.x += delta.x;
			absMouseOffset.y += delta.y;

			// to engine pos.
			delta.y = - delta.y;

			// logDebug("x=%d, y=%d, abs_x=%d, abs_y=%d", delta.x, delta.y, absMouseOffset.x, absMouseOffset.y);
		}
		else  {
			// delta = Point<int>(0, 0); // no delta if the mouse is not captured!
			delta.x = newPt.x - savedMousePt.x;
			delta.y = newPt.y - savedMousePt.y;

			// to engine pos.
			delta.y = - delta.y;
		}
		// some operations(eg. drag heightmap, paint texture onto terrain) consume lots of cpu cycles,
		// causing user feels lag when mouse moving, to avoid it we should remove last mouse move messages
		// from the message queue of the window each time after processed the current move message.
//#define HACK_MOUSE_MOVE
//		if (isMouseLocked) {
//			if ( delta.x || delta.y) {
//				//::SetCursorPos(savedMousePt.x, savedMousePt.y);
//				//std::cout << savedMousePt.x << "," << savedMousePt.x << std::endl;
//
//#ifndef HACK_MOUSE_MOVE
//				//// remove WM_MOUSEMOVE caused by ::SetCursorPos
//				//MSG msg;
//				//::PeekMessage(&msg, m_hWnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE);
//#endif
//			}
//		}
//		else
			savedMousePt = newPt;

		if(mCurInputListener) mCurInputListener->onMouseMove(pt.x, pt.y, delta.x, delta.y, flags);
		// logDebug("mouse move %f, x=%d, y = %d", Utility::GetTime(), pt.x, pt.y);

#ifdef HACK_MOUSE_MOVE
		// NOTE: remove last WM_MOUSEMOVE message from the queue.
		MSG msg;
		while (::PeekMessage(&msg, mHwnd, WM_MOUSEMOVE, WM_MOUSEMOVE, PM_REMOVE)) {};
#endif
					   }
					   break;
	case WM_LBUTTONDOWN:
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(wParam);
		if(mCurInputListener) mCurInputListener->onMouseDown(pt.x, pt.y, MB_Left, flags);
		break;
	case WM_LBUTTONUP:
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(wParam);
		if(mCurInputListener) mCurInputListener->onMouseUp(pt.x, pt.y, MB_Left, flags);
		break;
	case WM_MBUTTONDOWN:
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(wParam);
		if(mCurInputListener) mCurInputListener->onMouseDown(pt.x, pt.y, MB_Middle, flags);
		break;
	case WM_MBUTTONUP:
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(wParam);
		if(mCurInputListener) mCurInputListener->onMouseUp(pt.x, pt.y, MB_Middle, flags);
		break;
	case WM_RBUTTONDOWN:
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(wParam);
		if(mCurInputListener) mCurInputListener->onMouseDown(pt.x, pt.y, MB_Right, flags);
		break;
	case WM_RBUTTONUP:
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(wParam);
		if(mCurInputListener) mCurInputListener->onMouseUp(pt.x, pt.y, MB_Right, flags);
		break;
	case WM_MOUSEWHEEL:
		ptWin.x = GET_X_LPARAM(lParam); 
		ptWin.y = GET_Y_LPARAM(lParam); 
		pt= _pointWinClientToEngine(mHwnd, ptWin);
		flags = toEngineMouseFlags(LOWORD(wParam));
		if(mCurInputListener) mCurInputListener->onMouseWheel(pt.x, pt.y, ((int)GET_WHEEL_DELTA_WPARAM(wParam))/120, flags);
		break;
				}
			}

			// processed = true;
		}
		break;

	case WM_SYSCOMMAND:
		// disable F10(the default key to select menu)
		if (wParam == SC_KEYMENU)
			processed = true;
		break;

	case WM_ERASEBKGND:
		processed = true;
		break;

	//case WM_SETCURSOR: {
	//	if (hClientCursor && LOWORD(lParam) == HTCLIENT) {
	//		::SetCursor(hClientCursor);
	//		processed = true;
	//	}
	//	break;
	//				   }
	case WM_CLOSE:
		bWillEnd = true;
		break;
	default:
		processed = false;
	}

	return processed;
}